﻿package joint;


import core.*;
import java.io.*;
import java.net.*;


// ==================================== Класс приема/передачи сообщений телефона
// TODO переделать, чтобы сообщения записывались сразу в байт-массив
public class IO extends Thread {

//==================================================================== Константы
private final int QUERY_LEN = 32; // Макс. кол-во компонентов очереди

//=================================================================== Переменные
private boolean fRun = true; // Флаг времени жизни

// Сокет
private Socket socket; // Сокет соединения
private DataInputStream in; // Входной поток данных
private DataOutputStream out; // Исходящий поток данных


// Очередь
private byte[][] query; // Очередь сообщений
private int queryP; // Указатель на следующую своб. ячейку очереди
//private boolean fStarted = false; // Флаг начала отправки сообщения
private int lenP; // Номер в очереди для вставки
private int len; // Общая длина очереди, байт

// Разное
private int readLen; // Кол-во прочитанных байт


//================================================================ Инициализация
public IO() {

	this.start();
}


//================================================================ Закрыть сокет
private void closeSocket() {

	// Клиент отсоединен. Закрываем потоки и теряем их
	try {
		if (in != null) {
			in.close();
		}
		if (out != null) {
			out.close();
		}
	} catch (Exception e) {
		Mess.err("IO.closeSocket", "Не могу корректно закрыть сокет", e);
	}


	try {
		if (socket != null) {
			socket.close();
		}
	} catch (Exception e) {
		Mess.err("IO.closeSocket", "Не могу корректно закрыть сокет", e);
	}


	in = null;
	out = null;
	socket = null;
}


//================================================================ Разъединиться
public void close() {

	Mess.m("IO.close", "Закрываю работу класса");
	fRun = false;

	if (socket != null) {
		closeSocket();
	}

	query = null; // Очередь сообщений
}


//============================================================= Начало сообщения
public void start(int comType) {

	queryP = 0;

	sendInt(comType);
	len = 0;
	lenP = -1;
}


//============================================================== Конец сообщения
public void flush() throws IOException {

	int queryTot = queryP;

	if (lenP >= 0) {
		queryP = lenP;
		sendInt(len);
	}

	// Общий массив
	byte[] messAll = new byte[len + query[0].length];
	int messAllP = query[0].length;
	System.arraycopy(query[0], 0, messAll, 0, messAllP);

	for (int q = 1; q < queryTot; ++q) {
		System.arraycopy(query[q], 0, messAll, messAllP, query[q].length);
		messAllP += query[q].length;
	}

	out.write(messAll);
	out.flush();

}


//=============================================================== ФУНКЦИИ ЧТЕНИЯ
//====================================================== Начало чтения сообщения
public void startRead() {

	readLen = 0;
}


//===================================================== Выдать прочитанную длину
public int getReadLen() {

	return readLen;
}


//============================================================= Прочитать булево
public boolean readBool() throws IOException {

	if (readInt() == 0) {
		return false;
	} else {
		return true;
	}
}


//====================================================== Прочитать знаковый байт
/*
 * public byte readByte() throws IOException { readLen++; return in.readByte();
 * }
 */


//======================================================== Прочитать байт-массив
public void readBytes(byte[] bytes, int offset, int len) throws IOException {

	in.readFully(bytes, offset, len);
	readLen += len;
}


//============================================ Прочитать байт-массив фикс. длины
public byte[] readBytes(byte[] bytes) throws IOException {

	in.readFully(bytes);
	readLen += bytes.length;
	return bytes;
}


public byte[] readBytes(int length) throws IOException {

	byte[] bytes = new byte[length];

	in.readFully(bytes);
	readLen += length;

	return bytes;
}


//================================================== Прочитать беззнаковое целое
public int readInt() throws IOException {

	int byt; // Прочитанный байт
	int ret = 0; // Возвращаемая величина
	int offset = 0; // Смещение

	do {

		byt = in.readUnsignedByte();

		readLen++;
		ret += (byt & 0x7f) << offset;
		offset += 7;
	} while ((byt & Mat.BIT7) != 0);

	//	Mess.m("IO.readInt", "Прочитано: " + ret);

	return ret;
}


//=================================================== Прочитать беззнаковый байт
public int readUnsByte() throws IOException {

	readLen++;
	return in.readUnsignedByte();
}


//============================================================= Прочитать строку
public String readString() throws IOException {

	int len = readInt();
	return Mat.iso2utf(readBytes(len));
}


//============================================================= ФУНКЦИИ ОТПРАВКИ
//==================================================== Отправить булево значение
public void sendBool(boolean f) {

	if (f) {
		sendInt(1);
	} else {
		sendInt(0);
	}

}


//=============================================================== Отправить байт
public void sendByte(byte byt) {

	query[queryP++] = new byte[] { byt };
	len++;
}


//======================================================== Отправить байт-массив
public void sendBytes(byte[] bytes) {

	query[queryP++] = bytes;
	len += bytes.length;
}


//============================================================== Отправить целое
public void sendInt(int number) {

	// Первый байт - младший

	int byteCount = 1;
	int toSend;

	toSend = number;

	do {
		toSend >>>= 7;
		if (toSend > 0) { // Еще есть биты
			byteCount++;
		}
	} while (toSend > 0);

	query[queryP] = new byte[byteCount];

	for (byteCount = 0; byteCount < query[queryP].length; ++byteCount) {
		toSend = number & 0x7f; // Первые 7 бит
		number >>>= 7;
		if (number > 0) { // Еще есть биты
			toSend |= 0x80;
		}
		query[queryP][byteCount] = (byte) toSend;
	}
	len += query[queryP].length;
	queryP++;
}


// ============================================================= Место для длины
public void sendLen() {

	lenP = queryP++;
}


//============================================================= Отправить строку
public void sendString(String text) {

	sendInt(text.length());
	sendBytes(Mat.utf2iso(text));
}


//=================================================================== Соединение
@Override
public void run() {

	Thread.currentThread().setName("ioInit");

	try {

		socket = new Socket(Setting.server, Setting.port);

		in = new DataInputStream(socket.getInputStream());
		out = new DataOutputStream(socket.getOutputStream());

		Mess.m("IO.init", "Созданы потоки ввода/вывода");
		query = new byte[QUERY_LEN][];

		if (fRun) {
			Man.ioMan.connConnected();
		}

	} catch (IOException e) {
		if (fRun) {
			Man.ioMan.connNotConnected("Соединиться с сетью не удалось: сервер "
					+ Setting.server + " не отвечает", e);
		}
	} catch (Exception e) {
		if (fRun) {
			Man.ioMan.connNotConnected("Соединиться с сетью не удалось", e);
		}
	}

	Mess.m("IO.run", "Exit thread"); /**/
}

}
